#include <gtest/gtest.h>
#include "asio.hpp"
#include "syralink.hpp"
#include <thread>
#include <chrono>
#include <array>
#include <atomic>

using asio::ip::tcp;

class session : public std::enable_shared_from_this<session> {
  public:
    session(tcp::socket socket): socket_(std::move(socket)) { }

    void start()
    {
        do_read();
    }

  private:
    void do_read()
    {
        auto self(shared_from_this());
        socket_.async_read_some(asio::buffer(data_, max_length),
            [this, self](std::error_code ec, std::size_t length) {
                if(ec.value() == 0) {
                    do_write(length);
                } else if(ec == asio::error::eof) {
                    return;
                    // sl::loger::info("connection close");
                } else {
                    // sl::loger::error("read error: {}", ec.message());
                    return;
                }
            });
    }

    void do_write(std::size_t length)
    {
        auto self(shared_from_this());
        asio::async_write(socket_, asio::buffer(data_, length),
            [this, self](std::error_code ec, std::size_t /*length*/) {
                if(ec.value() == 0) {
                    do_read();
                } else {
                    return;
                    // sl::loger::error("write error: {}", ec.message());
                }
            });
    }

    tcp::socket socket_;
    enum {
        max_length = 1024
    };
    char data_[max_length];
};

class server {
  public:
    server(asio::io_context& io_context, short port):
        acceptor_(io_context, tcp::endpoint(tcp::v4(), port))
    {
        do_accept();
    }

  private:
    void do_accept()
    {
        acceptor_.async_accept([this](std::error_code ec, tcp::socket socket) {
            if(!ec) {
                // asio::ip::tcp::endpoint ep = socket.remote_endpoint();
                // sl::loger::info(
                //     "accept: {}:{}", ep.address().to_string(), ep.port());
                std::make_shared<session>(std::move(socket))->start();
            }

            do_accept();
        });
    }

    tcp::acceptor acceptor_;
};

constexpr static int     port           = 10009;
constexpr static int     SERVER_STOPSIG = SIGUSR1;
static std::atomic<bool> flag           = false;

class NetworkTest : public ::testing::Test {
    std::thread thrd;

  protected:
    void SetUp() override
    {
        thrd = std::thread(&NetworkTest::server_thrd, this);
    }

    void TearDown() override
    {
        ::kill(getpid(), SERVER_STOPSIG);
        if(thrd.joinable()) {
            thrd.join();
        }
    }

  private:
    void server_thrd()
    {
        try {
            asio::io_context io_context;
            asio::signal_set stop_signals(io_context, SERVER_STOPSIG);
            stop_signals.async_wait([&io_context](const std::error_code&, int) {
                io_context.stop();
                // sl::loger::info("tcp server stop");
            });

            server s(io_context, port);

            flag.store(true);
            io_context.run();
        } catch(const std::exception& e) {
            std::cerr << "Exception: " << e.what() << '\n';
        }
    }
};

int client_thrd()
{
    while(flag.load() == false) {
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }

    try {
        asio::io_context io_context;

        asio::ip::tcp::endpoint ep{
            asio::ip::make_address_v4("127.0.0.1"), port};
        tcp::socket s(io_context);
        s.connect(ep);

        std::string msg = "tcp echo message";
        s.send(asio::buffer(msg));

        std::array<sl::byte, 1024> data;
        size_t                     length = s.read_some(asio::buffer(data));
        if(length != msg.size()) {
            // sl::loger::error("recv error. length: {}", length);
            return __LINE__;
        }
        // sl::loger::info("recv: {}", length);
        // s.write_some(asio::buffer("hello"));
        // asio::write(s, asio::buffer("hello"));
    } catch(const std::exception& e) {
        std::cerr << "Exception: " << e.what() << '\n';
        return __LINE__;
    }

    return 0;
}

TEST_F(NetworkTest, ASIO_TcpEchoTest)
{
    int rc = client_thrd();
    EXPECT_EQ(rc, 0);
}
